package main

import (
	"embed"
	_ "embed"
	"encoding/json"
	"flag"
	"fmt"
	"github.com/bingoohuang/gg/pkg/goip"
	"html/template"
	"log"
	"net/http"
	"os"
	"regexp"
	"strings"
	"time"
)

//go:embed assets
var assets embed.FS

func main() {
	flag.Parse()

	http.HandleFunc("/", wrap(netstatWebHandler))
	http.HandleFunc("/netstat", wrap(netstat))
	log.Printf("start to listen at :8090")
	http.ListenAndServe(":8090", nil)
}

func netstatWebHandler(w http.ResponseWriter, r *http.Request) error {
	if r.URL.Path != "/" {
		http.NotFound(w, r)
		return nil
	}

	hostname, _ := os.Hostname()
	ipList, _ := goip.MainIP()

	lines, err := RunProgramName(netstatCmd, 3*time.Second)
	if err != nil {
		return err
	}

	netstatLines := ParseNetstatLines(lines, r.RemoteAddr)
	pidCache := map[string]int{}
	for i := range netstatLines {
		// 把 Pid 从 pidProgram 解析出来， pidProgram 的示例：2956625/beefs， -
		pid := NumReg.FindString(netstatLines[i].PidProgram)
		if pid == "" {
			continue
		}

		if j, ok := pidCache[pid]; ok {
			netstatLines[i].RES, netstatLines[i].PCPU, netstatLines[i].ProgramName =
				netstatLines[j].RES, netstatLines[j].PCPU, netstatLines[j].ProgramName
			continue
		}

		netstatLines[i].RES, netstatLines[i].PCPU = parseProcessInfo(pid)
		netstatLines[i].ProgramName = parseProcessName(pid)
		pidCache[pid] = i
	}

	data := map[string]interface{}{
		"Hostname":     hostname,
		"IP":           ipList,
		"ClientAddr":   r.RemoteAddr,
		"NetstatLines": netstatLines,
	}

	if r.URL.Query().Get("format") == "json" || r.Header.Get("Accept") == "application/json" {
		w.Header().Set("Content-Type", "application/json; charset=utf-8")
		return json.NewEncoder(w).Encode(data)
	}

	t, err := template.ParseFS(assets, "assets/*.html")
	if err != nil {
		return err
	}
	if err := t.ExecuteTemplate(w, "index.html", data); err != nil {
		return err
	}

	return nil
}

var NumReg = regexp.MustCompile(`\d+`)

func parseProcessInfo(pid string) (rss, pcpu string) {
	cmd := fmt.Sprintf("ps -wo rss,pcpu,args -p %s| tail -1", pid)
	lines, err := RunProgramName(cmd, 3*time.Second)
	if err != nil {
		log.Printf("error occured: %v", err)
		return
	}
	if len(lines) == 0 {
		log.Printf("no inforamtion for pid %s", pid)
		return
	}

	// example output
	//     PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
	// 2956625 d5k       20   0  819576  79704      4 S   0.0   2.1  49:39.80 beefs
	fields := strings.Fields(lines[0])
	return fields[0], fields[1]
}

func parseProcessName(pid string) (program string) {
	cmd := fmt.Sprintf("ps -p %s -wo args | tail -1", pid)
	lines, err := RunProgramName(cmd, 3*time.Second)
	if err != nil {
		log.Printf("error occured: %v", err)
		return
	}
	if len(lines) == 0 {
		log.Printf("no inforamtion for pid %s", pid)
		return
	}

	// example output
	// [d5k@VM-24-15-centos lishang]$ ps -p 2956625 -wo args
	// COMMAND
	// beefs -logdir=/home/d5k/weed/log/v1 volume -dir=/home/d5k/weed/volume/d1 -mserver=10.0.24.15:9331 -ip=10.0.24.15 -ip.bind=0.0.0.0 -port=8081 -max=12
	return lines[0]
}

func Abbreviate(str string, maxWidth int) string

func netstat(w http.ResponseWriter, req *http.Request) error {
	lines, err := RunProgramName(netstatCmd, 3*time.Second)
	if err != nil {
		return err
	}

	fmt.Fprintf(w, strings.Join(lines, "\n"))
	return nil
}
